/**
 * Copyright (c) Facebook, Inc. and its affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 *
 *
 * @format
 */

"use strict";
var _extends =
  Object.assign ||
  function(target) {
    for (var i = 1; i < arguments.length; i++) {
      var source = arguments[i];
      for (var key in source) {
        if (Object.prototype.hasOwnProperty.call(source, key)) {
          target[key] = source[key];
        }
      }
    }
    return target;
  };
function _asyncToGenerator(fn) {
  return function() {
    var gen = fn.apply(this, arguments);
    return new Promise(function(resolve, reject) {
      function step(key, arg) {
        try {
          var info = gen[key](arg);
          var value = info.value;
        } catch (error) {
          reject(error);
          return;
        }
        if (info.done) {
          resolve(value);
        } else {
          return Promise.resolve(value).then(
            function(value) {
              step("next", value);
            },
            function(err) {
              step("throw", err);
            }
          );
        }
      }
      return step("next");
    });
  };
}
function _objectWithoutProperties(obj, keys) {
  var target = {};
  for (var i in obj) {
    if (keys.indexOf(i) >= 0) continue;
    if (!Object.prototype.hasOwnProperty.call(obj, i)) continue;
    target[i] = obj[i];
  }
  return target;
}

const WorkerFarm = require("./WorkerFarm");

const assert = require("assert");
const fs = require("fs");
const getTransformCacheKey = require("./Transformer/getTransformCacheKey");
const path = require("path");
var _require = require("metro-cache");
const Cache = _require.Cache,
  stableHash = _require.stableHash;

class Transformer {
  constructor(config, getSha1Fn) {
    this._config = config;

    this._config.watchFolders.forEach(verifyRootExists);
    this._cache = new Cache(config.cacheStores);
    this._getSha1 = getSha1Fn;

    // Remove the transformer config params that we don't want to pass to the
    // transformer. We should change the config object and move them away so we
    // can treat the transformer config params as opaque.
    var _config$transformer = this._config.transformer;
    const _getTransformOptions = _config$transformer.getTransformOptions,
      _postMinifyProcess = _config$transformer.postMinifyProcess,
      _transformVariants = _config$transformer.transformVariants,
      _workerPath = _config$transformer.workerPath,
      transformerConfig = _objectWithoutProperties(_config$transformer, [
        "getTransformOptions",
        "postMinifyProcess",
        "transformVariants",
        "workerPath"
      ]);

    const transformerOptions = {
      transformerPath: this._config.transformerPath,
      transformerConfig
    };

    this._workerFarm = new WorkerFarm(config, transformerOptions);

    const globalCacheKey = getTransformCacheKey({
      cacheVersion: this._config.cacheVersion,
      projectRoot: this._config.projectRoot,
      transformerConfig: transformerOptions
    });

    this._baseHash = stableHash([globalCacheKey]).toString("binary");
  }

  transformFile(filePath, transformerOptions) {
    var _this = this;
    return _asyncToGenerator(function*() {
      const cache = _this._cache;
      const customTransformOptions = transformerOptions.customTransformOptions,
        dev = transformerOptions.dev,
        experimentalImportSupport =
          transformerOptions.experimentalImportSupport,
        hot = transformerOptions.hot,
        inlineRequires = transformerOptions.inlineRequires,
        minify = transformerOptions.minify,
        platform = transformerOptions.platform,
        type = transformerOptions.type,
        extra = _objectWithoutProperties(transformerOptions, [
          "customTransformOptions",
          "dev",
          "experimentalImportSupport",
          "hot",
          "inlineRequires",
          "minify",
          "platform",
          "type"
        ]);

      for (const key in extra) {
        if (hasOwnProperty.call(extra, key)) {
          throw new Error(
            "Extra keys detected: " + Object.keys(extra).join(", ")
          );
        }
      }

      const localPath = path.relative(_this._config.projectRoot, filePath);

      const partialKey = stableHash([
        // This is the hash related to the global Bundler config.
        _this._baseHash,

        // Path.
        localPath,

        customTransformOptions,
        dev,
        experimentalImportSupport,
        hot,
        inlineRequires,
        minify,
        platform,
        type
      ]);

      const sha1 = _this._getSha1(filePath);
      let fullKey = Buffer.concat([partialKey, Buffer.from(sha1, "hex")]);
      const result = yield cache.get(fullKey);

      // A valid result from the cache is used directly; otherwise we call into
      // the transformer to computed the corresponding result.
      const data = result
        ? { result, sha1 }
        : yield _this._workerFarm.transform(localPath, transformerOptions);

      // Only re-compute the full key if the SHA-1 changed. This is because
      // references are used by the cache implementation in a weak map to keep
      // track of the cache that returned the result.
      if (sha1 !== data.sha1) {
        fullKey = Buffer.concat([partialKey, Buffer.from(data.sha1, "hex")]);
      }

      cache.set(fullKey, data.result);

      return _extends({}, data.result, {
        getSource() {
          return fs.readFileSync(filePath);
        }
      });
    })();
  }

  end() {
    this._workerFarm.kill();
  }
}

function verifyRootExists(root) {
  // Verify that the root exists.
  assert(fs.statSync(root).isDirectory(), "Root has to be a valid directory");
}

module.exports = Transformer;
